﻿using System.Text;
using Cordyceps.Micro.Elastic.Options;
using Elastic.Clients.Elasticsearch;
using Elastic.Transport;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace Cordyceps.Micro.Elastic;

/// <summary>
///     Elasticsearch 客户端工厂（线程安全，推荐注册为单例）
/// </summary>
public class ElasticsearchClientFactory : IElasticsearchClientFactory
{
    private readonly ILogger<ElasticsearchClientFactory> _logger;

    private readonly IOptionsMonitor<ElasticsearchOptions> _optionsMonitor;

    /// <summary>
    ///     Elasticsearch 客户端工厂构造函数
    /// </summary>
    /// <param name="logger"></param>
    /// <param name="optionsMonitor"></param>
    public ElasticsearchClientFactory(
        ILogger<ElasticsearchClientFactory> logger,
        IOptionsMonitor<ElasticsearchOptions> optionsMonitor
    )
    {
        _logger = logger;
        _optionsMonitor = optionsMonitor;
    }

    /// <summary>
    ///     创建Elasticsearch客户端实例（同步方式）
    /// </summary>
    /// <param name="options">配置选项</param>
    /// <returns>已配置的ElasticsearchClient</returns>
    public ElasticsearchClient CreateClient(ElasticsearchOptions options)
    {
        ValidateOptions(options);
        var connectionPool = CreateConnectionPool(options);
        var settings = ConfigureClientSettings(connectionPool, options);
        // 创建并返回客户端实例
        return new ElasticsearchClient(settings);
    }

    /// <summary>
    ///     使用默认配置创建Elasticsearch客户端实例
    /// </summary>
    public ElasticsearchClient CreateClient()
    {
        var options = _optionsMonitor.CurrentValue;
        return CreateClient(options);
    }

    /// <summary>
    ///     根据配置创建连接池
    /// </summary>
    private NodePool CreateConnectionPool(ElasticsearchOptions options)
    {
        var nodes = options.Nodes.Distinct().Select(uri => new Node(uri)).ToList();

        return options.PoolType switch
        {
            ConnectionPoolType.Sniffing => new SniffingNodePool(nodes),
            ConnectionPoolType.Sticky => new StickyNodePool(nodes),
            ConnectionPoolType.Cloud => CreateCloudNodePool(options),
            _ => new StaticNodePool(nodes)
        };
    }

    /// <summary>
    ///     创建云连接池
    /// </summary>
    private NodePool CreateCloudNodePool(ElasticsearchOptions options)
    {
        ArgumentException.ThrowIfNullOrEmpty(options.CloudId, nameof(options.CloudId));
        ArgumentException.ThrowIfNullOrEmpty(options.Authentication?.ApiKey, nameof(options.Authentication.ApiKey));

        var credentials = new ApiKey(options.Authentication.ApiKey);
        return new CloudNodePool(options.CloudId, credentials);
    }

    /// <summary>
    ///     配置客户端设置
    /// </summary>
    private ElasticsearchClientSettings ConfigureClientSettings(
        NodePool connectionPool,
        ElasticsearchOptions options)
    {
        var settings = new ElasticsearchClientSettings(connectionPool)
            // 设置健康检查超时（建立连接时的等待时间）
            .PingTimeout(TimeSpan.FromSeconds(options.Timeouts.Ping))
            // 设置请求超时（完整请求的等待时间）
            .RequestTimeout(TimeSpan.FromSeconds(options.Timeouts.Request))
            // 设置最大重试次数
            .MaximumRetries(options.Retry.MaxRetries)
            .MaxRetryTimeout(TimeSpan.FromSeconds(options.Retry.MaxRetryTimeout))
            // 最大连接数
            .ConnectionLimit(options.MaxConnections)
            // 请求压缩：对于大量数据传输启用压缩
            .EnableHttpCompression(options.EnableHttpCompression)
            //启用在发出请求时收集有关线程池的统计信息
            .EnableThreadPoolStats(options.EnableThreadPoolMonitoring);
        // 配置证书验证
        if (options.DisableCertificateValidation)
            settings.ServerCertificateValidationCallback((sender, certificate, chain, errors) => true);

        // 配置认证
        ConfigureAuthentication(settings, options);
        // 配置默认索引
        ConfigureDefaultIndex(settings, options);
        // 配置调试模式
        ConfigureDebugging(settings, options);
        return settings;
    }

    /// <summary>
    ///     配置认证方式
    /// </summary>
    private static void ConfigureAuthentication(ElasticsearchClientSettings settings, ElasticsearchOptions options)
    {
        if (options.Authentication is null) return;

        if (!string.IsNullOrEmpty(options.Authentication.ApiKey))
            settings.Authentication(new ApiKey(options.Authentication.ApiKey));
        else if (!string.IsNullOrEmpty(options.Authentication.Username))
            settings.Authentication(new BasicAuthentication(
                options.Authentication.Username,
                options.Authentication.Password));
    }

    /// <summary>
    ///     配置默认索引
    /// </summary>
    private void ConfigureDefaultIndex(
        ElasticsearchClientSettings settings,
        ElasticsearchOptions options)
    {
        if (!string.IsNullOrEmpty(options.DefaultIndex)) settings.DefaultIndex(options.DefaultIndex);
    }

    /// <summary>
    ///     配置调试模式（记录请求/响应内容）
    /// </summary>
    private void ConfigureDebugging(
        ElasticsearchClientSettings settings,
        ElasticsearchOptions options)
    {
        if (!options.Debug.Enable) return;
        // 启用调试模式
        settings.EnableDebugMode();
        // 如果需要记录请求或响应体
        if (options.Debug.LogRequests || options.Debug.LogResponses)
            // 禁用直接流式传输以捕获原始内容
            settings.DisableDirectStreaming()
                .OnRequestCompleted(details =>
                {
                    // 记录请求内容
                    if (options.Debug.LogRequests && details.RequestBodyInBytes != null)
                        _logger.LogDebug("Request: {Method} {Uri}\n{Body}",
                            details.HttpMethod,
                            details.Uri,
                            Encoding.UTF8.GetString(details.RequestBodyInBytes));

                    // 记录响应内容
                    if (options.Debug.LogResponses && details.ResponseBodyInBytes != null)
                        _logger.LogDebug("Response ({StatusCode}):\n{Body}",
                            details.HttpStatusCode,
                            Encoding.UTF8.GetString(details.ResponseBodyInBytes));
                });
    }

    /// <summary>
    ///     验证配置参数有效性
    /// </summary>
    private static void ValidateOptions(ElasticsearchOptions options)
    {
        ArgumentNullException.ThrowIfNull(options.Nodes);

        if (options.Nodes.Count == 0)
            throw new ArgumentException("至少需要配置一个Elasticsearch节点");

        if (options.PoolType == ConnectionPoolType.Cloud && string.IsNullOrEmpty(options.CloudId))
            throw new ArgumentException("使用Cloud连接池时必须提供CloudId");
    }
}